/*
 * @(#) $Header$
 *
 * Copyright (C) 2007  Forklabs Daniel Léonard
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package ca.forklabs.media.jai.opimage;

import java.awt.RenderingHints;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferDouble;
import java.awt.image.DataBufferFloat;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.util.Collection;
import javax.media.jai.CollectionImage;
import javax.media.jai.ImageLayout;
import com.sun.media.jai.opimage.RIFUtil;
import ca.forklabs.media.jai.ImageFunction3D;
import ca.forklabs.media.jai.RasterAdapter;
import ca.forklabs.media.jai.SimpleCollectionImage;

/**
 * Class {@code ImageFunction3DOpImage} is the {@link CollectionImage} for
 * operator <em>imagefunction3d</em>.
 *
 * @author   <a href="mailto:forklabs at gmail.com?subject=ca.forklabs.media.jai.opimage.ImageFunction3DOpImage">Daniel Léonard</a>
 * @version $Revision$
 */
public class ImageFunction3DOpImage extends SimpleCollectionImage {

//---------------------------
// Class variables
//---------------------------

// exceptionnally, class variables are at the end


//---------------------------
// Constructors
//---------------------------

   /**
    * Constructor.
    * @param   function   the functional description.
    * @param   width   the width of the image.
    * @param   height   the height of the image.
    * @param   depth   the depth of the image.
    * @param   hints   rendering hints.
    */
   public ImageFunction3DOpImage(ImageFunction3D function, int width, int height, int depth, RenderingHints hints) {
      this.create(function, width, height, depth, hints);
      }


//---------------------------
// Instance methods
//---------------------------

   /**
    * Gets the data type. The default data type is {@code float}. If a sample
    * model is provided in the rendering hints, its data type will be respected,
    * provided it is {@code float} or {@code double}.
    * @param   hints   rendering hints.
    * @return   the data type of the image.
    */
   protected int getDataType(RenderingHints hints) {
      int type = DataBuffer.TYPE_FLOAT;

      ImageLayout layout = RIFUtil.getImageLayoutHint(hints);
      if ((null != layout) && layout.isValid(ImageLayout.SAMPLE_MODEL_MASK)) {
         SampleModel model = layout.getSampleModel(null);
         type = model.getDataType();
         }

      return type;
      }

   /**
    * Really creates the image.
    * @param   function   the functional description.
    * @param   width   the width of the image.
    * @param   height   the height of the image.
    * @param   depth   the depth of the image.
    * @param   specialization   the pixel specialization.
    * @param   <T>   the type of data, either <code>float[]</code> or
    *                <code>double[]</code>.
    */
   @SuppressWarnings({ "hiding", "unchecked", "rawtypes" })
   protected <T> void create(ImageFunction3D function, int width, int height, int depth, PixelSpecialization<T> specialization) {
      Collection sinks = this.getImages();

      boolean is_complex = function.isComplex();
      int len = width * height;
      int bands = function.getNumBands();
      if (is_complex) {
         bands *= 2;
         }

      for (int d = 0; d < depth; d++) {
         T[] data = specialization.newDataArray(bands, len);

         for (int b = 0, incr = (is_complex) ? 2 : 1; b < bands; b += incr) {
            T real = data[b];
            T imag = (is_complex) ? data[b + 1] : null;
            specialization.generateBand(function, b, d, width, height, depth, real, imag);
            }

         DataBuffer buffer = specialization.newDataBuffer(data, len);

         RenderedImage sink = RasterAdapter.buildImage(buffer, width, height);
         sinks.add(sink);
         }
      }

   /**
    * Creates a float image.
    * @param   function   the functional description.
    * @param   width   the width of the image.
    * @param   height   the height of the image.
    * @param   depth   the depth of the image.
    */
   protected void createFloat(ImageFunction3D function, int width, int height, int depth) {
      PixelSpecialization<float[]> specialization = ImageFunction3DOpImage.FLOAT_SPECIALIZATION;
      this.create(function, width, height, depth, specialization);
      }

   /**
    * Creates a double image.
    * @param   function   the functional description.
    * @param   width   the width of the image.
    * @param   height   the height of the image.
    * @param   depth   the depth of the image.
    */
   protected void createDouble(ImageFunction3D function, int width, int height, int depth) {
      PixelSpecialization<double[]> specialization = ImageFunction3DOpImage.DOUBLE_SPECIALIZATION;
      this.create(function, width, height, depth, specialization);
      }

   /**
    * Creates the image.
    * @param   function   the functional description.
    * @param   width   the width of the image.
    * @param   height   the height of the image.
    * @param   depth   the depth of the image.
    * @param   hints   rendering hints.
    */
   protected void create(ImageFunction3D function, int width, int height, int depth, RenderingHints hints) {
      int data_type = this.getDataType(hints);
      switch (data_type) {
         case DataBuffer.TYPE_DOUBLE:
            this.createDouble(function, width, height, depth);
            break;
         default:
            this.createFloat(function, width, height, depth);
            break;
         }
      }


//---------------------------
// Inner classes
//---------------------------

   /**
    * Class {@code PixelSpecialization} specializes pixel manipulation on the
    * array type.
    *
    * @param   <T>   the array type (such as {@code float[]} or
    *                {@code double[]}).
    *
    * @author   <a href="mailto:forklabs at gmail.com?subject=ca.forklabs.media.jai.operator.ImageFunction3DOpImageOpImage$PixelSpecialization">Daniel Léonard</a>
    * @version $Revision: 1.2 $
    */
   protected static abstract class PixelSpecialization<T> {

      /**
       * Creates a new data array to hold all the pixels of one image in the collection.
       * @param   bands   the number of bands.
       * @param   len   the number of pixels.
       * @return   a new data array.
       */
      public abstract T[] newDataArray(int bands, int len);

      /**
       * Generates the given band.
       * @param   function   the functional description.
       * @param   band   the band of the image.
       * @param   slice   the image position in the collection.
       * @param   width   the width of the image.
       * @param   height   the height of the image.
       * @param   depth   the number of images in the collection.
       * @param   real   placeholder array for the real values.
       * @param   imag   placeholder array for the imaginary values,
       *                 {@code null} if the image is NOT complex.
       */
      public abstract void generateBand(ImageFunction3D function, int band, int slice, int width, int height, int depth, T real, T imag);

      /**
       * Creates a new data buffer to hold the data array.
       * @param   data   the pixel data.
       * @param   size   the number of pixels in the image.
       * @return   a new data buffer.
       */
      public abstract DataBuffer newDataBuffer(T[] data, int size);

      }


//---------------------------
// Class variables
//---------------------------

   /** Specialization for the <em>float</em> data type. */
   protected static final PixelSpecialization<float[]> FLOAT_SPECIALIZATION = new PixelSpecialization<float[]>() {

      @Override
      public float[][] newDataArray(int bands, int len) {
         float[][] data = new float[bands][len];
         return data;
         }

      @Override
      public void generateBand(ImageFunction3D function, int band, int slice, int width, int height, int depth, float[] real, float[] imag) {
         function.getBand(band, slice, width, height, depth, real, imag);
         }

      @Override
      public DataBuffer newDataBuffer(float[][] data, int size) {
         DataBuffer buffer = new DataBufferFloat(data, size);
         return buffer;
         }

      };

   /** Specialization for the <em>double</em> data type. */
   protected static final PixelSpecialization<double[]> DOUBLE_SPECIALIZATION = new PixelSpecialization<double[]>() {

      @Override
      public double[][] newDataArray(int bands, int len) {
         double[][] data = new double[bands][len];
         return data;
         }

      @Override
      public void generateBand(ImageFunction3D function, int band, int slice, int width, int height, int depth, double[] real, double[] imag) {
         function.getBand(band, slice, width, height, depth, real, imag);
         }

      @Override
      public DataBuffer newDataBuffer(double[][] data, int size) {
         DataBuffer buffer = new DataBufferDouble(data, size);
         return buffer;
         }

      };

   }
